package util;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import sun.misc.Unsafe;

@SuppressWarnings("removal")
public class UnsafeUtil {
	public static final int ARRAY_BYTE_BASE_OFFSET = Unsafe.ARRAY_BYTE_BASE_OFFSET;
	public static final Unsafe unsafe;
	private static final MethodHandle getDeclaredFields0MH;
	private static final MethodHandle getDeclaredMethods0MH;
	private static final long OVERRIDE_OFFSET;

	static {
		try {
			Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
			theUnsafeField.setAccessible(true);
			unsafe = (Unsafe)theUnsafeField.get(null);
			for (long i = 8; ; i++) {
				if (unsafe.getBoolean(theUnsafeField, i)) {
					theUnsafeField.setAccessible(false);
					if (!unsafe.getBoolean(theUnsafeField, i)) {
						OVERRIDE_OFFSET = i;
						break;
					}
					theUnsafeField.setAccessible(true);
				}
				if (i == 32) // should be enough
					throw new UnsupportedOperationException(System.getProperty("java.version"));
			}
			var lookup = MethodHandles.lookup();
			getDeclaredFields0MH = lookup.unreflect(setAccessible(
					Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class)));
			getDeclaredMethods0MH = lookup.unreflect(setAccessible(
					Class.class.getDeclaredMethod("getDeclaredMethods0", boolean.class)));
		} catch (ReflectiveOperationException e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	// javac: --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
	// java:  --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
	public static void main(String[] args) throws Exception {
//		Class.forName("jdk.internal.misc.Unsafe").getModule().addExports("jdk.internal.misc", UnsafeUtil.class.getModule());
//		var u = jdk.internal.misc.Unsafe.getUnsafe();
//		System.out.println(u);
	}

	private UnsafeUtil() {
	}

	public static <T extends AccessibleObject> T setAccessible(T ao) {
		unsafe.putBoolean(ao, OVERRIDE_OFFSET, true);
		return ao;
	}

	public static Field[] getDeclaredFields(Class<?> klass) {
		try {
			return (Field[])getDeclaredFields0MH.invokeExact(klass, false);
		} catch (RuntimeException | Error e) {
			throw e;
		} catch (Throwable e) { // MethodHandle.invoke
			throw new RuntimeException(e);
		}
	}

	public static Method[] getDeclaredMethods(Class<?> klass) {
		try {
			return (Method[])getDeclaredMethods0MH.invokeExact(klass, false);
		} catch (RuntimeException | Error e) {
			throw e;
		} catch (Throwable e) { // MethodHandle.invoke
			throw new RuntimeException(e);
		}
	}

	public static Field getField(Class<?> klass, String fieldName) {
		for (Field field : getDeclaredFields(klass))
			if (field.getName().equals(fieldName))
				return setAccessible(field);
		throw new IllegalStateException("not found field '" + fieldName + "' in " + klass.getName());
	}

	public static Method getMethod(Class<?> klass, String methodName, Class<?>... paramTypes) {
		for (Method method : getDeclaredMethods(klass)) {
			if (method.getName().equals(methodName) && method.getParameterCount() == paramTypes.length) {
				var types = method.getParameterTypes();
				for (int i = 0; ; i++) {
					if (i == paramTypes.length)
						return setAccessible(method);
					if (types[i] != paramTypes[i])
						break;
				}
			}
		}
		throw new IllegalStateException("not found method '" + methodName + "' in " + klass.getName());
	}

	public static long objectFieldOffset(Class<?> klass, String fieldName) {
		return unsafe.objectFieldOffset(getField(klass, fieldName));
	}
}
